{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A8MVXQUFkX3n"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "IcfrhafzkZbH"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zOSK2IBG6-9Z"
      },
      "source": [
        "# Обучение и оценка с Keras\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7s-G0Fajsvjn"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/train_and_evaluate\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/train_and_evaluate.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/train_and_evaluate.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/guide/keras/train_and_evaluate.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fj66ZXAzrJC2"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tc92ghg-sw2w"
      },
      "source": [
        "Это руководство охватывает обучение, оценку и прогнозирование (выводы) моделей в TensorFlow 2.0 в двух общих ситуациях:\n",
        "\n",
        "- При использовании встроенных API для обучения и валидации (таких как `model.fit()`, `model.evaluate()`, `model.predict()`). Этому посвящен раздел **\"Использование встроенных циклов обучения и оценки\"**.\n",
        "- При написании пользовательских циклов с нуля с использованием eager execution и объекта `GradientTape`. Эти вопросы рассматриваются в разделе **\"Написание собственных циклов обучения и оценки с нуля\"**.\n",
        "\n",
        "В целом, независимо от того, используете ли вы встроенные циклы или пишете свои собственные, обучение и оценка моделей работает строго одинаково для всех видов моделей Keras: Sequential моделей, моделей, созданных с помощью Functional API, и написанных с нуля с использованием наследованных моделей.\n",
        "\n",
        "Это руководство не охватывает распределенное обучение."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RNG8OH3yuWrb"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Uh_b0kqEuYah"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "try:\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "tf.keras.backend.clear_session()  # Для простого сброса состояния ноутбука."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "052DbsQ175lP"
      },
      "source": [
        "## Часть I: Использование встроенных циклов обучения и оценки\n",
        "\n",
        "При передаче данных во встроенные обучающие циклы модели вы должны либо использовать **массивы Numpy** (если ваши данные малы и умещаются в памяти), либо объекты **tf.data Dataset**. В следующих нескольких параграфах мы будем использовать набор данных MNIST в качестве массива Numpy, чтобы показать, как использовать оптимизаторы, функции потерь и метрики."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rXDWPajQ8V8V"
      },
      "source": [
        "### Обзор API: первый полный пример\n",
        "\n",
        "Давайте рассмотрим следующую модель (здесь мы строим ее с помощью Functional API, но она может быть и Sequential или субклассированной моделью):\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_eJxUppsNjMj"
      },
      "outputs": [],
      "source": [
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "C02iiuYZNklq"
      },
      "source": [
        "Вот как выглядит типичный сквозной процесс работы, состоящий из обучения, проверки на отложенных данных, сгенерированных из исходных данных обучения, и, наконец, оценки на тестовых данных:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YTzpaClpNvyJ"
      },
      "outputs": [],
      "source": [
        "# Загрузим учебный датасет для этого примера\n",
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "\n",
        "# Предобработаем данные (это массивы Numpy)\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
        "\n",
        "y_train = y_train.astype('float32')\n",
        "y_test = y_test.astype('float32')\n",
        "\n",
        "# Зарезервируем 10,000 примеров для валидации\n",
        "x_val = x_train[-10000:]\n",
        "y_val = y_train[-10000:]\n",
        "x_train = x_train[:-10000]\n",
        "y_train = y_train[:-10000]\n",
        "\n",
        "# Укажем конфигурацию обучения (оптимизатор, функция потерь, метрики)\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(),  # Оптимизатор\n",
        "              # Минимизируемая функция потерь\n",
        "              loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "              # Список метрик для мониторинга\n",
        "              metrics=[keras.metrics.SparseCategoricalAccuracy()])\n",
        "\n",
        "# Обучим модель разбив данные на \"пакеты\"\n",
        "# размером \"batch_size\", и последовательно итерируя\n",
        "# весь датасет заданное количество \"эпох\"\n",
        "print('# Обучаем модель на тестовых данных')\n",
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=64,\n",
        "                    epochs=3,\n",
        "                    # Мы передаем валидационные данные для\n",
        "                    # мониторинга потерь и метрик на этих данных\n",
        "                    # в конце каждой эпохи\n",
        "                    validation_data=(x_val, y_val))\n",
        "\n",
        "# Возвращаемый объект \"history\" содержит записи\n",
        "# значений потерь и метрик во время обучения\n",
        "print('\\nhistory dict:', history.history)\n",
        "\n",
        "# Оценим модель на тестовых данных, используя `evaluate`\n",
        "print('\\n# Оцениваем на тестовых данных')\n",
        "results = model.evaluate(x_test, y_test, batch_size=128)\n",
        "print('test loss, test acc:', results)\n",
        "\n",
        "# Сгенерируем прогнозы (вероятности -- выходные данные последнего слоя)\n",
        "# на новых данных с помощью `predict`\n",
        "print('\\n# Генерируем прогнозы для 3 образцов')\n",
        "predictions = model.predict(x_test[:3])\n",
        "print('размерность прогнозов:', predictions.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R43f-GWfDkQ5"
      },
      "source": [
        "### Определение потерь, метрик и оптимизатора\n",
        "\n",
        "Для обучения модели с помощью `fit`, вам нужно задать функцию потерь, оптимизатор, и опционально некоторые метрики для мониторинга.\n",
        "\n",
        "Вам нужно передать их модели в качестве аргументов метода `compile()`:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CiB85T_hM_vQ"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "              metrics=[keras.metrics.SparseCategoricalAccuracy()])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lq3wsghNM_7C"
      },
      "source": [
        "Аргумент `metrics` должен быть списком -- ваша модель может иметь любое количество метрик.\n",
        "\n",
        "Если у вашей модели несколько выходов, вы можете задать различные функции потерь и метрики для каждого выхода,\n",
        "и вы можете регулировать вклад каждого выхода в общее значение потерь модели. Вы найдете больше деталей об этом в разделе \"**Передача данных в модели с несколькими входами и выходами**\".\n",
        "\n",
        "Обратите внимание, что во многих случаях потери и метрики задаются с помощью строковых идентификаторов:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pOQOcScsNiT3"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss='sparse_categorical_crossentropy',\n",
        "              metrics=['sparse_categorical_accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IVHOVTqFknZP"
      },
      "source": [
        "Для последующего переиспользования поместим определение нашей модели и шаг компиляции в функции; мы будем вызывать их несколько раз в разных примерах этого руководства."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "G3Zrjsf_kt8k"
      },
      "outputs": [],
      "source": [
        "def get_uncompiled_model():\n",
        "  inputs = keras.Input(shape=(784,), name='digits')\n",
        "  x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "  x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "  outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "  model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "  return model\n",
        "\n",
        "def get_compiled_model():\n",
        "  model = get_uncompiled_model()\n",
        "  model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss='sparse_categorical_crossentropy',\n",
        "              metrics=['sparse_categorical_accuracy'])\n",
        "  return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_1rDyyQEtC8E"
      },
      "source": [
        "#### Вам доступно множество встроенных оптимизаторов, функций потерь и метрик\n",
        "\n",
        "Как правило, вам не нужно создавать с нуля собственные функции потерь, метрики, или оптимизаторы, поскольку то, что вам нужно, скорее всего, уже является частью Keras API:\n",
        "\n",
        "Оптимизаторы:\n",
        "- `SGD()` (с или без momentum)\n",
        "- `RMSprop()`\n",
        "- `Adam()`\n",
        "- и т.д.\n",
        "\n",
        "Функции потерь:\n",
        "- `MeanSquaredError()`\n",
        "- `KLDivergence()`\n",
        "- `CosineSimilarity()`\n",
        "- и т.д.\n",
        "\n",
        "Метрики:\n",
        "- `AUC()`\n",
        "- `Precision()`\n",
        "- `Recall()`\n",
        "- и т.д."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LH1QjaltNm2U"
      },
      "source": [
        "#### Кастомные функции потерь\n",
        "\n",
        "Есть два способа обеспечить кастомные функции потерь с Keras. В примере создается функция принимающая на вход `y_true` и `y_pred`. Следующий пример показывает функцию потерь вычисляющую среднее расстояние между реальными данными и прогнозами:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IIn3_iD0Nyll"
      },
      "outputs": [],
      "source": [
        "def basic_loss_function(y_true, y_pred):\n",
        "    return tf.math.reduce_mean(y_true - y_pred)\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.Adam(),\n",
        "              loss=basic_loss_function)\n",
        "\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RZqpsybNN_Wh"
      },
      "source": [
        "Если вам нужна функция потерь у которой есть иные параметры кроме `y_true` и `y_pred`, вы можете субклассировать класс `tf.keras.losses.Loss` и реализовать следующие два метода:\n",
        "\n",
        "* `__init__(self)` —Принять параметры, передаваемые при вызове вашей функции потерь \n",
        "* `call(self, y_true, y_pred)` —Использовать цели (`y_true`) и предсказания модели (`y_pred`) для вычисления потерь модели\n",
        "\n",
        "Параметры передаваемые в `__init__()` могут быть использованы во время `call()` при вычислении потерь.\n",
        "\n",
        "Следующий пример показывает как реализовать функцию потерь `WeightedCrossEntropy` которая вычисляет `BinaryCrossEntropy`, где потери конкретного класса или всей функции могут быть модифицированы при помощи скаляра."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gr996axuOCfX"
      },
      "outputs": [],
      "source": [
        "class WeightedBinaryCrossEntropy(keras.losses.Loss):\n",
        "    \"\"\"\n",
        "    Args:\n",
        "      pos_weight: Скалярный вес для положительных меток функции потерь.\n",
        "      weight: Скалярный вес для всей функции потерь.\n",
        "      from_logits: Вычислять ли потери от логитов или вероятностей.\n",
        "      reduction: Тип tf.keras.losses.Reduction для применения к функции потерь.\n",
        "      name: Имя функции потерь.\n",
        "    \"\"\"\n",
        "    def __init__(self, pos_weight, weight, from_logits=False,\n",
        "                 reduction=keras.losses.Reduction.AUTO,\n",
        "                 name='weighted_binary_crossentropy'):\n",
        "        super(WeightedBinaryCrossEntropy, self).__init__(reduction=reduction,\n",
        "                                                         name=name)\n",
        "        self.pos_weight = pos_weight\n",
        "        self.weight = weight\n",
        "        self.from_logits = from_logits\n",
        "\n",
        "    def call(self, y_true, y_pred):\n",
        "        if not self.from_logits:\n",
        "            # Вручную посчитаем взвешенную кросс-энтропию.\n",
        "            # Формула следующая qz * -log(sigmoid(x)) + (1 - z) * -log(1 - sigmoid(x))\n",
        "            # где z - метки, x - логиты, а q - веса.\n",
        "            # Поскольку переданные значения от сигмоиды (предположим в этом случае)\n",
        "            # sigmoid(x) будет заменено y_pred\n",
        "\n",
        "            # qz * -log(sigmoid(x)) 1e-6 добавляется как эпсилон, чтобы не передать нуль в логарифм\n",
        "            x_1 = y_true * self.pos_weight * -tf.math.log(y_pred + 1e-6)\n",
        "\n",
        "            # (1 - z) * -log(1 - sigmoid(x)). Добавляем эпсилон, чтобы не пропустить нуль в логарифм\n",
        "            x_2 = (1 - y_true) * -tf.math.log(1 - y_pred + 1e-6)\n",
        "\n",
        "            return tf.add(x_1, x_2) * self.weight \n",
        "\n",
        "        # Используем встроенную функцию\n",
        "        return tf.nn.weighted_cross_entropy_with_logits(y_true, y_pred, self.pos_weight) * self.weight\n",
        "\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.Adam(),\n",
        "              loss=WeightedBinaryCrossEntropy(0.5, 2))\n",
        "\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YUkJi7ENDnCg"
      },
      "source": [
        "#### Кастомные метрики\n",
        "\n",
        "Если вам нужны метрики не являющиеся частью API, вы можете легко создать кастомные метрики субклассировав класс `Metric`. Вам нужно реализовать 4 метода:\n",
        "\n",
        "- `__init__(self)`,  в котором вы создадите переменные состояния для своей метрики.\n",
        "- `update_state(self, y_true, y_pred, sample_weight=None)`, который использует ответы `y_true` и предсказания модели `y_pred` для обновления переменных состояния.\n",
        "- `result(self)`, использующий переменные состояния для вычисления конечного результата.\n",
        "- `reset_states(self)`, который переинициализирует состояние метрики.\n",
        "\n",
        "Обновление состояния и вычисление результатов хранятся отдельно (в `update_state()` и `result()` соответственно), потому что в некоторых случаях вычисление результатов может быть очень дорогим и будет выполняться только периодически.\n",
        "\n",
        "Вот простой пример, показывающий, как реализовать метрику `CategoricalTruePositives`, которая считает сколько элементов были правильно классифицированы, как принадлежащие к данному классу:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MbSNO6uePVsF"
      },
      "outputs": [],
      "source": [
        "class CategoricalTruePositives(keras.metrics.Metric):\n",
        "\n",
        "    def __init__(self, name='categorical_true_positives', **kwargs):\n",
        "      super(CategoricalTruePositives, self).__init__(name=name, **kwargs)\n",
        "      self.true_positives = self.add_weight(name='tp', initializer='zeros')\n",
        "\n",
        "    def update_state(self, y_true, y_pred, sample_weight=None):\n",
        "      y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))\n",
        "      values = tf.cast(y_true, 'int32') == tf.cast(y_pred, 'int32')\n",
        "      values = tf.cast(values, 'float32')\n",
        "      if sample_weight is not None:\n",
        "        sample_weight = tf.cast(sample_weight, 'float32')\n",
        "        values = tf.multiply(values, sample_weight)\n",
        "      self.true_positives.assign_add(tf.reduce_sum(values))\n",
        "\n",
        "    def result(self):\n",
        "      return self.true_positives\n",
        "\n",
        "    def reset_states(self):\n",
        "      # Состояние метрики будет сброшено в начале каждой эпохи.\n",
        "      self.true_positives.assign(0.)\n",
        "\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "              metrics=[CategoricalTruePositives()])\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=3)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HipGsW7qDo0Q"
      },
      "source": [
        "#### Обработка функций потерь и метрик, не соответствующих стандартной сигнатуре\n",
        "\n",
        "Подавляющее большинство потерь и метрик может быть вычислено из `y_true` и `y_pred`, где `y_pred` - вывод вашей модели. Но не все. Например, потери регуляризации могут требовать только активацию слоя (в этом случае нет целевых значений), и эта активация может не являться выходом модели.\n",
        "\n",
        "В таких случаях вы можете вызвать `self.add_loss(loss_value)` из метода `call` кастомного слоя. Вот простой пример, который добавляет регуляризацию активности (отметим что регуляризация активности встроена во все слои Keras  -- этот слой используется только для приведения конкретного примера):\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CrYwrDR0PWab"
      },
      "outputs": [],
      "source": [
        "class ActivityRegularizationLayer(layers.Layer):\n",
        "\n",
        "  def call(self, inputs):\n",
        "    self.add_loss(tf.reduce_sum(inputs) * 0.1)\n",
        "    return inputs  # Проходной слой.\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "\n",
        "# Вставим регуляризацию активности в качестве слоя\n",
        "x = ActivityRegularizationLayer()(x)\n",
        "\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss='sparse_categorical_crossentropy')\n",
        "\n",
        "# Полученные потери будут намного больше чем раньше\n",
        "# из-за компонента регуляризации.\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fWnqsdwt06us"
      },
      "source": [
        "Вы можете сделать то же самое для логирования значений метрик:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZBYYhVgJ0973"
      },
      "outputs": [],
      "source": [
        "class MetricLoggingLayer(layers.Layer):\n",
        "\n",
        "  def call(self, inputs):\n",
        "    # Аргумент `aggregation` определяет\n",
        "    # как аггрегировать попакетные значения\n",
        "    # в каждой эпохе:\n",
        "    # в этом случае мы просто усредняем их.\n",
        "    self.add_metric(keras.backend.std(inputs),\n",
        "                    name='std_of_activation',\n",
        "                    aggregation='mean')\n",
        "    return inputs  # Проходной слой.\n",
        "\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "\n",
        "# Вставка логирования std в качестве слоя.\n",
        "x = MetricLoggingLayer()(x)\n",
        "\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "              loss='sparse_categorical_crossentropy')\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f9_9XQvA0Jap"
      },
      "source": [
        "В [Functional API](functional.ipynb), вы можете также вызвать `model.add_loss(loss_tensor)`, или `model.add_metric(metric_tensor, name, aggregation)`.\n",
        "\n",
        "Вот простой пример:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WrA1yFql0gXg"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x1 = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "x2 = layers.Dense(64, activation='relu', name='dense_2')(x1)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x2)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "\n",
        "model.add_loss(tf.reduce_sum(x1) * 0.1)\n",
        "\n",
        "model.add_metric(keras.backend.std(x1),\n",
        "                 name='std_of_activation',\n",
        "                 aggregation='mean')\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss='sparse_categorical_crossentropy')\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sJqpjGyMAa82"
      },
      "source": [
        "#### Автоматическое выделение валидационного отложенного множества\n",
        "\n",
        "В первом полном примере, как вы видели, мы использовали аргумент `validation_data` для передачи кортежа\n",
        "массивов Numpy `(x_val, y_val)` модели для оценки валидационных потерь и метрик в конце каждой эпохи.\n",
        "\n",
        "Вот другая опция: аргумент `validation_split` позволяет вам автоматически зарезервировать часть ваших тренировочных данных для валидации. Значением аргумента является доля данных, которые должны быть зарезервированы для валидации, поэтому значение должно быть больше 0 и меньше 1. Например, `validation_split=0.2` значит \"используйте 20% данных для валидации\", а `validation_split=0.6` значит \"используйте 60% данных для валидации\".\n",
        "\n",
        "Валидация вычисляется *взятием последних x% записей массивов полученных вызовом `fit`, перед любым перемешиванием*.\n",
        "\n",
        "Вы можете использовать `validation_split` только когда обучаете данными Numpy."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Z-DQyeRePYS-"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1, steps_per_epoch=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Uq0TDb15DBbc"
      },
      "source": [
        "### Обучение и оценка с tf.data Dataset\n",
        "\n",
        "В последних нескольких параграфах вы видели, как обрабатывать потери, метрики и оптимизаторы, и посмотрели, как использовать аргументы `validation_data` и `validation_split` в `fit`, когда ваши данные передаются в виде массивов Numpy.\n",
        "\n",
        "Давайте теперь рассмотрим случай, когда ваши данные поступают в форме tf.data Dataset.\n",
        "\n",
        "tf.data API это набор утилит в TensorFlow 2.0 для загрузки и предобработки данных быстрым и масштабируемым способом.\n",
        "\n",
        "Для полного руководства по созданию Dataset-ов, см. [документацию tf.data](https://www.tensorflow.org/guide/data).\n",
        "\n",
        "Вы можете передать экземпляр Dataset напрямую в методы `fit()`, `evaluate()` и `predict()`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iY27EJsmENBj"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Сперва давайте создадим экземпляр тренировочного Dataset.\n",
        "# Для нашего примера мы будем использовать те же данные MNIST что и ранее.\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "# Перемешаем и нарежем набор данных.\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Сейчас получим тестовый датасет.\n",
        "test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))\n",
        "test_dataset = test_dataset.batch(64)\n",
        "\n",
        "# Поскольку датасет уже позаботился о разбивке на пакеты,\n",
        "# мы не передаем аргумент `batch_size`.\n",
        "model.fit(train_dataset, epochs=3)\n",
        "\n",
        "# Вы можете также оценить модель или сделать прогнозы на датасете.\n",
        "print('\\n# Оценка')\n",
        "model.evaluate(test_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r2yh2h79ENUy"
      },
      "source": [
        "Заметьте, что Dataset сбрасывается в конце каждой эпохи, поэтому он может быть переиспользован в следующей эпохе.\n",
        "\n",
        "Если вы хотите учиться только на определенном количестве пакетов из этого Dataset, вы можете передать аргумент `steps_per_epoch`, который указывает, сколько шагов обучения должна выполнить модель, используя этот Dataset, прежде чем перейти к следующей эпохе.\n",
        "\n",
        "В этом случае датасет не будет сброшен в конце каждой эпохи, вместо этого мы просто продолжим обрабатывать следующие пакеты. В датасете в конечном счете закончатся данные (если только это не зацикленный бесконечно датасет)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7t0V6K_EEdB7"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Подготовка учебного датасета\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Использовать только 100 пакетов за эпоху (это 64 * 100 примеров)\n",
        "model.fit(train_dataset.take(100), epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_e3b3rSmC-fL"
      },
      "source": [
        "#### Использование валидационного датасета\n",
        "\n",
        "Вы можете передать экземпляр Dataset как аргумент `validation_data` в `fit`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pv_53VE2C-11"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Подготовим учебный датасет\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Подготовим валидационный датасет\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(64)\n",
        "\n",
        "model.fit(train_dataset, epochs=3, validation_data=val_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J2Z7pgmSC7Zk"
      },
      "source": [
        "В конце каждой эпохи модель будет проходить по валидационному Dataset и вычислять потери и валидационные метрики.\n",
        "\n",
        "Если вы хотите запустить проверку только на определенном количестве пакетов из этого Dataset, вы можете передать аргумент` validation_steps`, который указывает, сколько шагов валидации должна выполнить модель до прерывания валидации и перехода к следующей эпохе:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "eGa4DQA0C8Mu"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Подготовка тренировочных данных\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Подготовка валидационных данных\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(64)\n",
        "\n",
        "model.fit(train_dataset, epochs=3,\n",
        "          # Запускаем валидацию только на первых 10 пакетах датасета\n",
        "          # используя аргумент `validation_steps`\n",
        "          validation_data=val_dataset, validation_steps=10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6qo-fWETC4sD"
      },
      "source": [
        "Обратите внимание, что валидационный Dataset будет сбрасываться после каждого использования (так что вы всегда будете получать оценку на одних и тех же примерах от эпохи к эпохе).\n",
        "\n",
        "Аргумент `validation_split` (генерирующий отложенную выборку из тренировочных данных) не поддерживается при обучении на объектах Dataset, поскольку для этого требуется возможность индексирования элементов, что невозможно в общем в Dataset API."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cIGt6NNqC2ZR"
      },
      "source": [
        "### Другие поддерживаемые форматы входных данных\n",
        "\n",
        "Кроме массивов Numpy и TensorFlow Dataset-ов, возможно обучить модель Keras с использованием датафрейма Pandas , или с генераторами Python которые выдают значения пакетами.\n",
        "\n",
        "В общем, мы рекомендуем вам использовать входные данные Numpy если их количество невелико и помещается в памяти, и Dataset-ы в других случаях."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Bid1XnTl_zNT"
      },
      "source": [
        "### Использование весов для примеров и классов\n",
        "\n",
        "Кроме входных данных и меток модели можно передавать веса примеров и веса классов при использовании `fit`:\n",
        "\n",
        "- При обучении на данных Numpy: с помощью аргументов `sample_weight` и `class_weight`.\n",
        "- При обучении на Dataset-ах: если Dataset вернет кортеж `(input_batch, target_batch, sample_weight_batch)` .\n",
        "\n",
        "Массив \"sample weights\" это массив чисел которые определяют какой вес придать каждому элементу в пакете при вычислении значения потерь. Это обычно используется в несбалансированных задачах классификации (суть в том, чтобы придать больший вес редко встречающимся классам). Когда используемые веса равны единицам и нулям, массив можно использовать в качестве *маски* для функции потерь (полностью исключая вклад определенных элементов в общее значение потерь).\n",
        "\n",
        "Словарь \"class weights\" является более специфичным экземпляром той же концепции: он сопоставляет индексы классов с весам которые должны быть использованы для примеров принадлежащих этому классу. Например, если класс \"0\" представлен втрое меньше чем класс \"1\" в ваших данных, вы можете использовать `class_weight={0: 1., 1: 0.5}`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rbH4jT5Hp_gg"
      },
      "source": [
        "Вот пример Numpy веса классов или веса элементов чтобы придать большее значение корректной классификации класса #5 (соответствующий цифре \"5\" в датасете MNIST)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0Y7QBNUXWTva"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "class_weight = {0: 1., 1: 1., 2: 1., 3: 1., 4: 1.,\n",
        "                # Установим вес \"2\" для класса \"5\",\n",
        "                # сделав этот класс в 2x раз важнее\n",
        "                5: 2.,\n",
        "                6: 1., 7: 1., 8: 1., 9: 1.}\n",
        "print('Fit with class weight')\n",
        "model.fit(x_train, y_train,\n",
        "          class_weight=class_weight,\n",
        "          batch_size=64,\n",
        "          epochs=4)\n",
        "\n",
        "# Вот тот же пример использующий `sample_weight`:\n",
        "sample_weight = np.ones(shape=(len(y_train),))\n",
        "sample_weight[y_train == 5] = 2.\n",
        "print('\\nОбучение с весом класса')\n",
        "\n",
        "model = get_compiled_model()\n",
        "model.fit(x_train, y_train,\n",
        "          sample_weight=sample_weight,\n",
        "          batch_size=64,\n",
        "          epochs=4)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Qf1KvM0pqBaV"
      },
      "source": [
        "Вот соответствующий Dataset пример:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LHD60GshxSpf"
      },
      "outputs": [],
      "source": [
        "sample_weight = np.ones(shape=(len(y_train),))\n",
        "sample_weight[y_train == 5] = 2.\n",
        "\n",
        "# Создадим  Dataset включающий веса элементов\n",
        "# (3-тий элемент в возвращаемом кортеже).\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices(\n",
        "    (x_train, y_train, sample_weight))\n",
        "\n",
        "# Перемешаем и нарежем датасет.\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "model = get_compiled_model()\n",
        "model.fit(train_dataset, epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jXt0xQvDxMeq"
      },
      "source": [
        "### Передача данных в модели с несколькими входами и выходами\n",
        "\n",
        "В предыдущих примерах, мы рассматривали модель с единственным входом (тензор размера `(764,)`) и одним выходом (тензор прогнозов размера `(10,)`). Но как насчет моделей, у которых есть несколько входов или выходов?\n",
        "\n",
        "Рассмотрим следующую модель, в которой на входными данными являются изображения размера `(32, 32, 3)` (это `(высота, ширина, каналы)`) и временные ряды размера `(None, 10)` (это `(временные шаги, признаки)`). У нашей модели будет два выхода вычисленных из комбинации этих входов: a \"score\" (размерности `(1,)`) и вероятностное распределение по пяти классам (размерности `(5,)`).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QNUSGfKq1cZ-"
      },
      "outputs": [],
      "source": [
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "\n",
        "image_input = keras.Input(shape=(32, 32, 3), name='img_input')\n",
        "timeseries_input = keras.Input(shape=(None, 10), name='ts_input')\n",
        "\n",
        "x1 = layers.Conv2D(3, 3)(image_input)\n",
        "x1 = layers.GlobalMaxPooling2D()(x1)\n",
        "\n",
        "x2 = layers.Conv1D(3, 3)(timeseries_input)\n",
        "x2 = layers.GlobalMaxPooling1D()(x2)\n",
        "\n",
        "x = layers.concatenate([x1, x2])\n",
        "\n",
        "score_output = layers.Dense(1, name='score_output')(x)\n",
        "class_output = layers.Dense(5, activation='softmax', name='class_output')(x)\n",
        "\n",
        "model = keras.Model(inputs=[image_input, timeseries_input],\n",
        "                    outputs=[score_output, class_output])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cIWhMd7p3NGp"
      },
      "source": [
        "Давайте начертим эту модель, чтобы вы ясно увидели что мы здесь делаем (заметьте что размерности, которые вы видите на схеме это размерности пакетов, а не поэлементные размерности)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gKVQ4Y573Q_c"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vAauLxiT278G"
      },
      "source": [
        "Во время компиляции мы можем указать разные функции потерь для разных выходов, передав функции потерь в виде списка:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vDa7JSz93phE"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(),\n",
        "          keras.losses.CategoricalCrossentropy()])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bQip1Fex4-D5"
      },
      "source": [
        "Если мы передаем только одну функцию потерь модели, она будет применена к каждому выходу, что здесь не подходит.\n",
        "\n",
        "Аналогично для метрик:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Tkl5LMGi4_gK"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(),\n",
        "          keras.losses.CategoricalCrossentropy()],\n",
        "    metrics=[[keras.metrics.MeanAbsolutePercentageError(),\n",
        "              keras.metrics.MeanAbsoluteError()],\n",
        "             [keras.metrics.CategoricalAccuracy()]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "w_WWk9b1374P"
      },
      "source": [
        "Так как мы дали имена нашим выходным слоям, мы могли бы также указать функции потерь и метрики для каждого выхода в dict:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1MEctLBD4APc"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={'score_output': keras.losses.MeanSquaredError(),\n",
        "          'class_output': keras.losses.CategoricalCrossentropy()},\n",
        "    metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),\n",
        "                              keras.metrics.MeanAbsoluteError()],\n",
        "             'class_output': [keras.metrics.CategoricalAccuracy()]})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NVXRX0PX6lIn"
      },
      "source": [
        "Мы рекомендуем использовать имена и словари если у вас более 2 выходов."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RX3FTDWw4BOA"
      },
      "source": [
        "Имеется возможность присвоить разные веса разным функциям потерь (например, в нашем примере мы можем захотеть отдать предпочтение потере \"score\", увеличив в 2 раза важность потери класса), используя аргумент `loss_weights`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mgVK3xAv4JNv"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={'score_output': keras.losses.MeanSquaredError(),\n",
        "          'class_output': keras.losses.CategoricalCrossentropy()},\n",
        "    metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),\n",
        "                              keras.metrics.MeanAbsoluteError()],\n",
        "             'class_output': [keras.metrics.CategoricalAccuracy()]},\n",
        "    loss_weights={'score_output': 2., 'class_output': 1.})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vd8VzIu-3p0n"
      },
      "source": [
        "Вы можете также не вычислять потери для некоторых выходов, если эти выходы предполагаются только для прогнозирования, но не для обучения:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aUyohARI4Kqn"
      },
      "outputs": [],
      "source": [
        "# Функции потерь списком\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[None, keras.losses.CategoricalCrossentropy()])\n",
        "\n",
        "# Функции потерь словарем\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={'class_output': keras.losses.CategoricalCrossentropy()})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n0MwZc184MG_"
      },
      "source": [
        "Передача данных в модель с несколькими входами и выходами в `fit` работает аналогично тому, как мы определяем функцию потерь в `compile`:\n",
        "вы можете передать *списки массивов Numpy (совпадающие 1:1 с выходами на которых есть функции потерь)* или *словари сопоставляющие имена выходов массивам Numpy тренировочных данных*."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "So4cYwSW4la8"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(),\n",
        "          keras.losses.CategoricalCrossentropy()])\n",
        "\n",
        "# Сгенерируем случайные Numpy данные\n",
        "img_data = np.random.random_sample(size=(100, 32, 32, 3))\n",
        "ts_data = np.random.random_sample(size=(100, 20, 10))\n",
        "score_targets = np.random.random_sample(size=(100, 1))\n",
        "class_targets = np.random.random_sample(size=(100, 5))\n",
        "\n",
        "# Обучаемся на списках\n",
        "model.fit([img_data, ts_data], [score_targets, class_targets],\n",
        "          batch_size=32,\n",
        "          epochs=3)\n",
        "\n",
        "# Альтернативно, обучаемся на словарях\n",
        "model.fit({'img_input': img_data, 'ts_input': ts_data},\n",
        "          {'score_output': score_targets, 'class_output': class_targets},\n",
        "          batch_size=32,\n",
        "          epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "m7Ah1QUf4ll6"
      },
      "source": [
        "Ниже пример для Dataset: также как мы сделали для массивов Numpy, Dataset должен возвращать\n",
        "кортеж словарей."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vx34-FDZ4tdJ"
      },
      "outputs": [],
      "source": [
        "train_dataset = tf.data.Dataset.from_tensor_slices(\n",
        "    ({'img_input': img_data, 'ts_input': ts_data},\n",
        "     {'score_output': score_targets, 'class_output': class_targets}))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "model.fit(train_dataset, epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y_gqVFcHAAga"
      },
      "source": [
        "### Использование колбеков\n",
        "\n",
        "Колбеки в Keras это объекты которые вызываются в разных местах во время обучения (в начале эпохи, в конце пакета, в конце эпохи, и т.д.) и которые могут быть использованы для реализации такого поведения, как:\n",
        "\n",
        " Выполнение валидации в различных точках во время обучения (кроме встроенной валидации в конце каждой эпохи)\n",
        "- Установление контрольных точек модели через регулярные интервалы или когда она превышает определенный порог точности\n",
        "- Изменение скорости обучения модели, когда кажется что обучение перестает сходиться\n",
        "- Тонкая настройка верхних слоев, когда кажется что обучение перестает сходиться\n",
        "- Отправка электронных писем или сообщений, когда обучение заканчивается или когда превышен определенный порог производительности\n",
        "- и т.д.\n",
        "\n",
        "Колбеки могут переданы списком для вашего вызова `fit`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pNEhXWcnSG9B"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "callbacks = [\n",
        "    keras.callbacks.EarlyStopping(\n",
        "        # Прекратить обучение если `val_loss` больше не улучшается\n",
        "        monitor='val_loss',\n",
        "        # \"больше не улучшается\" определим как \"не лучше чем 1e-2 и меньше\"\n",
        "        min_delta=1e-2,\n",
        "        # \"больше не улучшается\" далее определим как \"как минимум в течение 2 эпох\"\n",
        "        patience=2,\n",
        "        verbose=1)\n",
        "]\n",
        "model.fit(x_train, y_train,\n",
        "          epochs=20,\n",
        "          batch_size=64,\n",
        "          callbacks=callbacks,\n",
        "          validation_split=0.2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PE3VK3WpSHL_"
      },
      "source": [
        "#### Доступно большое количество встроенных колбеков\n",
        "\n",
        "- `ModelCheckpoint`: Периодических сохраняет модель.\n",
        "- `EarlyStopping`: Останавливает обучение, в том случае когда валидационная метрика прекращает улучшаться.\n",
        "- `TensorBoard`: периодически пишет логи модели которые могут быть визуализированы в TensorBoard (больше деталей в разделе \"Визуализация\").\n",
        "- `CSVLogger`: стримит значения потерь и метрик в файл CSV.\n",
        "- и т.д.\n",
        "\n",
        "\n",
        "\n",
        "#### Написание собственного колбека\n",
        "\n",
        "Вы можете создать собственный колбек расширив базовый класс keras.callbacks.Callback. Колбек имеет доступ к ассоциированной модели посредством свойства класса `self.model`.\n",
        "\n",
        "Вот простой пример сохранения списка значений попакетных потерь во время обучения:\n",
        "\n",
        "```python\n",
        "class LossHistory(keras.callbacks.Callback):\n",
        "\n",
        "    def on_train_begin(self, logs):\n",
        "        self.losses = []\n",
        "\n",
        "    def on_batch_end(self, batch, logs):\n",
        "        self.losses.append(logs.get('loss'))\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mC08S-e-yOd7"
      },
      "source": [
        "### Сохранение контрольных точек моделей\n",
        "\n",
        "Когда вы обучаете модель на относительно больших датасетах, крайне важно сохранять чекпоинты вашей модели через определенные промежутки времени.\n",
        "\n",
        "Проще всего сделать это с помощью колбека `ModelCheckpoint`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "yEER-qc-xXNC"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "callbacks = [\n",
        "    keras.callbacks.ModelCheckpoint(\n",
        "        filepath='mymodel_{epoch}.h5',\n",
        "        # Путь по которому нужно сохранить модель\n",
        "        # Два параметра ниже значат что мы перезапишем\n",
        "        # текущий чекпоинт в том и только в том случае, когда\n",
        "        # улучится значение `val_loss`.\n",
        "        save_best_only=True,\n",
        "        monitor='val_loss',\n",
        "        verbose=1)\n",
        "]\n",
        "model.fit(x_train, y_train,\n",
        "          epochs=3,\n",
        "          batch_size=64,\n",
        "          callbacks=callbacks,\n",
        "          validation_split=0.2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "E1AZEf3ixXm8"
      },
      "source": [
        "Вы также можете написать собственный колбек для сохранения и восстановления моделей.\n",
        "\n",
        "Полное руководство по сериализации и сохранению, см. [Руководство по сохранению и сериализации моделей](./save_and_serialize.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "j22FtN-z_-Wj"
      },
      "source": [
        "### Использование расписаний скорости обучения\n",
        "\n",
        "Обычным паттерном при тренировке моделей глубокого обучения является постепенное сокращение скорости обучения по мере тренировки модели. Это общеизвестно как \"снижение скорости обучения\".\n",
        "\n",
        "График снижения скорости может быть как статичным (зафиксированным заранее, как функция от индекса текущей эпохи или текущего пакета) так и динамическим (зависящим от текущего поведения модели в частности от потери на валидации).\n",
        "\n",
        "#### Передача расписания оптимизатору\n",
        "\n",
        "Вы можете легко использовать график статического снижения скорости обучения передав объект расписания в качестве аргумента `learning_rate` вашему оптимизатору:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Cu-pnB_ctEav"
      },
      "outputs": [],
      "source": [
        "initial_learning_rate = 0.1\n",
        "lr_schedule = keras.optimizers.schedules.ExponentialDecay(\n",
        "    initial_learning_rate,\n",
        "    decay_steps=100000,\n",
        "    decay_rate=0.96,\n",
        "    staircase=True)\n",
        "\n",
        "optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9Ltf0Xtrtw-2"
      },
      "source": [
        "Доступно несколько встроенных схем снижения скорости обучения: `ExponentialDecay`, `PiecewiseConstantDecay`, `PolynomialDecay` и `InverseTimeDecay`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "13CvYhBptEjh"
      },
      "source": [
        "#### Использование колбеков для реализации графика динамического изменения скорости обучения\n",
        "\n",
        "Расписание динамического изменения скорости обучения (например, уменьшение скорости обучения, когда потери при валидации более не улучшаются) не может быть достигнуто с этими объектами расписания, поскольку оптимизатор не имеет доступа к показателям валидации.\n",
        "\n",
        "Однако колбеки имеют доступ ко всем метрикам, включая метрики валидации! Поэтому, вы можете достичь этого паттерна, используя колбек, который изменяет текущую скорость обучения на оптимизаторе. Фактически, есть и встроенный колбек ` ReduceLROnPlateau`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tnUhGslABdvx"
      },
      "source": [
        "### Визуализация потерь и метрик во время обучения\n",
        "\n",
        "Лучший способ следить за вашей моделью во время обучения - это использовать [TensorBoard] (https://www.tensorflow.org/tensorboard) - приложение на основе браузера, которое вы можете запустить локально и которое предоставляет вам:\n",
        "\n",
        "- Живые графики функции потерь и метрик для обучения и оценки\n",
        "- (опционально) Визуализации гистограмм активаций ваших слоев\n",
        "- (опционально) 3D-визуализации пространств вложения, изученных вашими слоями `Embedding`\n",
        "\n",
        "Если вы установили TensorFlow с помощью pip, вы можете запустить TensorBoard из командной строки:\n",
        "\n",
        "```\n",
        "tensorboard --logdir=/full_path_to_your_logs\n",
        "```\n",
        "\n",
        "#### Использование колбека TensorBoard\n",
        "\n",
        "Самый легкий способ использовать TensorBoard с моделью Keras и методом `fit` - это колбек `TensorBoard`.\n",
        "\n",
        "В простейшем случае просто укажите, куда вы хотите, чтобы колбек писал логи, и все готово:\n",
        "\n",
        "```python\n",
        "tensorboard_cbk = keras.callbacks.TensorBoard(log_dir='/full_path_to_your_logs')\n",
        "model.fit(dataset, epochs=10, callbacks=[tensorboard_cbk])\n",
        "```\n",
        "\n",
        "Колбек `TensorBoard` имеет много полезных опций, в том числе, писать ли лог вложений, гистограмм и как часто писать логи:\n",
        "\n",
        "```python\n",
        "keras.callbacks.TensorBoard(\n",
        "  log_dir='/full_path_to_your_logs',\n",
        "  histogram_freq=0,  # Как часто писать лог визуализаций гистограмм\n",
        "  embeddings_freq=0,  # Как часто писать лог визуализаций вложений\n",
        "  update_freq='epoch')  # Как часто писать логи (по умолчанию: однажды за эпоху)\n",
        "```\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5r5ZnFry7-B7"
      },
      "source": [
        "## Часть II: Написание собственных циклов обучения и оценки с нуля\n",
        "\n",
        "Если вам нужен более низкий уровень для ваших циклов обучения и оценки, чем тот что дают `fit()` и `evaluate()`, вы должны написать свои собственные. Это на самом деле довольно просто! Но вы должны быть готовы к большему количеству отладки."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HaRvt057_37D"
      },
      "source": [
        "### Использование GradientTape: первый полный пример\n",
        "\n",
        "Вызов модели внутри области видимости `GradientTape` позволяет получить градиенты обучаемых весов слоя относительно значения потерь. Используя экземпляр оптимизатора, вы можете использовать эти градиенты для обновления переменных (которые можно получить с помощью `model.trainable_weights`).\n",
        "\n",
        "Давайте переиспользуем нашу первоначальную модель MNIST из первой части и обучим ее, используя мини-пакетный градиентный спуск с кастомным циклом обучения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XQqxREEL66Kg"
      },
      "outputs": [],
      "source": [
        "# Получим модель.\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, name='predictions')(x)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "\n",
        "# Создадим экземпляр оптимизатора.\n",
        "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n",
        "# Создадимм экземпляр функции потерь.\n",
        "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n",
        "\n",
        "# Подготовим тренировочный датасет.\n",
        "batch_size = 64\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)\n",
        "\n",
        "# Итерируем по эпохам.\n",
        "epochs = 3\n",
        "for epoch in range(epochs):\n",
        "  print('Начинаем эпоху %d' % (epoch,))\n",
        "\n",
        "  # Итерируем по пакетам в датасете.\n",
        "  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "\n",
        "    # Откроем GradientTape чтобы записать операции\n",
        "    # выполняемые во время прямого прохода, включающего автодифференцирование.\n",
        "    with tf.GradientTape() as tape:\n",
        "\n",
        "      # Запустим прямой проход слоя.\n",
        "      # Операции применяемые слоем к своим\n",
        "      # входным данным будут записаны\n",
        "      # на GradientTape.\n",
        "      logits = model(x_batch_train, training=True)  # Логиты для минибатчей\n",
        "\n",
        "      # Вычислим значение потерь для этого минибатча.\n",
        "      loss_value = loss_fn(y_batch_train, logits)\n",
        "\n",
        "    # Используем gradient tape для автоматического извлечения градиентов\n",
        "    #  обучаемых переменных относительно потерь.\n",
        "    grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "\n",
        "    # Выполним один шаг градиентного спуска обновив\n",
        "    # значение переменных минимизирующих потери.\n",
        "    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "\n",
        "    # Пишем лог каждые 200 пакетов.\n",
        "    if step % 200 == 0:\n",
        "        print('Потери на обучении (для одного пакета) на шаге %s: %s' % (step, float(loss_value)))\n",
        "        print('Уже увидено: %s примеров' % ((step + 1) * 64))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1lt9Yva8xgCs"
      },
      "source": [
        "### Низкоуровневая обработка метрик\n",
        "\n",
        "Давайте рассмотрим метрики. Вы можете легко использовать встроенные метрики (или собственные, которые вы написали) в таких, написанных с нуля, циклах обучения. Вот последовательность действий:\n",
        "\n",
        "- Создайте экземпляр метрики в начале цикла\n",
        "- Вызовите `metric.update_state()` после каждого пакета\n",
        "- Вызовите `metric.result()` когда вам нужно показать текущее значение метрики\n",
        "- Вызовите `metric.reset_states()` когда вам нужно очистить состояние метрики (обычно в конце каждой эпохи)\n",
        "\n",
        "Давайте используем это знание, чтобы посчитать `SparseCategoricalAccuracy` на валидационных данных в конце каждой эпохи:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JDKgp70UxwdR"
      },
      "outputs": [],
      "source": [
        "# Получим модель\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "\n",
        "# Создадим экземпляр оптимизатора для обучения модели.\n",
        "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n",
        "# Создадим экземпляр функции потерь.\n",
        "loss_fn = keras.losses.SparseCategoricalCrossentropy()\n",
        "\n",
        "# Подготовим метрику.\n",
        "train_acc_metric = keras.metrics.SparseCategoricalAccuracy()\n",
        "val_acc_metric = keras.metrics.SparseCategoricalAccuracy()\n",
        "\n",
        "# Подготовим тренировочный датасет.\n",
        "batch_size = 64\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)\n",
        "\n",
        "# Подготовим валидационный датасет.\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(64)\n",
        "\n",
        "\n",
        "# Итерируем по эпохам.\n",
        "epochs = 3\n",
        "for epoch in range(epochs):\n",
        "  print('Начало эпохи %d' % (epoch,))\n",
        "\n",
        "  # Итерируем по пакетам в датасете.\n",
        "  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "    with tf.GradientTape() as tape:\n",
        "      logits = model(x_batch_train)\n",
        "      loss_value = loss_fn(y_batch_train, logits)\n",
        "    grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "\n",
        "    # Обновляем метрику на обучении.\n",
        "    train_acc_metric(y_batch_train, logits)\n",
        "\n",
        "    # Пишем лог каждые 200 пакетов.\n",
        "    if step % 200 == 0:\n",
        "        print('Потери на обучении (за один пакет) на шаге %s: %s' % (step, float(loss_value)))\n",
        "        print('Уже просмотрено: %s примеров' % ((step + 1) * 64))\n",
        "\n",
        "  # Покажем метрики в конце каждой эпохи.\n",
        "  train_acc = train_acc_metric.result()\n",
        "  print('Accuracy на обучении за эпоху: %s' % (float(train_acc),))\n",
        "  # Сбросим тренировочные метрики в конце каждой эпохи\n",
        "  train_acc_metric.reset_states()\n",
        "\n",
        "  # Запустим валидационный цикл в конце эпохи.\n",
        "  for x_batch_val, y_batch_val in val_dataset:\n",
        "    val_logits = model(x_batch_val)\n",
        "    # Обновим валидационные метрики\n",
        "    val_acc_metric(y_batch_val, val_logits)\n",
        "  val_acc = val_acc_metric.result()\n",
        "  val_acc_metric.reset_states()\n",
        "  print('Accuracy на валидации: %s' % (float(val_acc),))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rjraqJAonBJK"
      },
      "source": [
        "### Низкоуровневая обработка дополнительных потерь\n",
        "\n",
        "В предыдущем разделе вы видели, что для слоя можно добавить потери регуляризации, вызвав `self.add_loss(value)` в методе `call`.\n",
        "\n",
        "В общем вы можете захотеть учесть эти потери в своих пользовательских циклах обучения (если только вы сами не написали модель и уже знаете, что она не создает таких потерь).\n",
        "\n",
        "Вспомните пример из предыдущего раздела, где есть слой, который создает потери регуляризации:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fglnb_wzwtxh"
      },
      "outputs": [],
      "source": [
        "class ActivityRegularizationLayer(layers.Layer):\n",
        "\n",
        "  def call(self, inputs):\n",
        "    self.add_loss(1e-2 * tf.reduce_sum(inputs))\n",
        "    return inputs\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "# Вставим регуляризацию активности в качестве слоя\n",
        "x = ActivityRegularizationLayer()(x)\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "44zwXYXdwwss"
      },
      "source": [
        "Когда вы вызываете модель как тут:\n",
        "\n",
        "```python\n",
        "logits = model(x_train)\n",
        "```\n",
        "\n",
        "потери которые она создает во время прямого прохода добавляются в атрибут `model.losses`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KOWuXgDlxIbS"
      },
      "outputs": [],
      "source": [
        "logits = model(x_train[:64])\n",
        "print(model.losses)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EvkOERFYymF_"
      },
      "source": [
        "Отслеживаемые потери сначала очищаются в начале модели `__call__`, поэтому вы увидите только потери, созданные во время текущего одного прямого прохода. Например, при повторном вызове модели и последующем запросе к `losses` отображаются только последние потери, создано во время последнего вызова:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MZh9ek_NzEtQ"
      },
      "outputs": [],
      "source": [
        "logits = model(x_train[:64])\n",
        "logits = model(x_train[64: 128])\n",
        "logits = model(x_train[128: 192])\n",
        "print(model.losses)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MmftRMi6zWLN"
      },
      "source": [
        "Чтобы учесть эти потери во время обучения, все, что вам нужно сделать, это модифицировать цикл обучения, добавив к полному значению потерь `sum(model.losses)`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ueLhIG1mzdFp"
      },
      "outputs": [],
      "source": [
        "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n",
        "\n",
        "epochs = 3\n",
        "for epoch in range(epochs):\n",
        "  print('Начало эпохи %d' % (epoch,))\n",
        "\n",
        "  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "    with tf.GradientTape() as tape:\n",
        "      logits = model(x_batch_train)\n",
        "      loss_value = loss_fn(y_batch_train, logits)\n",
        "\n",
        "      # Добавляем дополнительные потери, созданные во время прямого прохода:\n",
        "      loss_value += sum(model.losses)\n",
        "\n",
        "    grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "\n",
        "    # Пишем лог каждые 200 пакетов.\n",
        "    if step % 200 == 0:\n",
        "        print('Ошибка на обучении (за один пакет) на шаге %s: %s' % (step, float(loss_value)))\n",
        "        print('Просмотрено: %s примеров' % ((step + 1) * 64))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wCOe4e4dxsB0"
      },
      "source": [
        "Это была последняя часть пазла! Вы достигли конца руководства.\n",
        "\n",
        "Сейчас вы знаете все, что нужно об использовании встроенных циклов обучения и написании своих собственных с нуля.\n"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "train_and_evaluate.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
